﻿
namespace CmdArgs.Settings
{
    using System.Collections.Generic;
    using CmdArgs.Validation;
    using CmdArgs.Validation.Options;

    /// <summary>
    /// Sice <see cref="OptionSettings"/> class is immutable, 
    /// it's construction may be easily done using this builder.
    /// This builder adopts so called fluent interface.
    /// </summary>
    public sealed class OptionSettingsBuilder
    {
        private IList<string> longNameAliases = new List<string>();
        private IList<char> shortNameAliases = new List<char>();
        private IList<IOptionValidator> validators = new List<IOptionValidator>();
        private string description = string.Empty;
        private string defaultValue = string.Empty;
        private IOptionParametersCount parametersCount;
        private string parameterName;

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionSettingsBuilder"/> class.
        /// </summary>
        /// <param name="longName">The first long name alias.
        /// All long names aliases have equivalent meaning, but at least one short or long alias must be provided.</param>
        /// <param name="shortName">The first short name alias.
        /// All short names aliases have equivalent meaning, but at least one short or long alias must be provided.</param>
        /// <param name="parametersCount">The parameters count policy class. 
        /// Use factory methods from class <see cref="OptionParametersCount"/>.</param>
        public OptionSettingsBuilder(string longName, char shortName, IOptionParametersCount parametersCount)
        {
            this.longNameAliases.Add(longName);
            this.shortNameAliases.Add(shortName);
            this.parametersCount = parametersCount;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionSettingsBuilder"/> class.
        /// </summary>
        /// <param name="shortName">The first short name alias.
        /// All short names aliases have equivalent meaning, but at least one short or long alias must be provided.</param>
        /// <param name="parametersCount">The parameters count policy class. 
        /// Use factory methods from class <see cref="OptionParametersCount"/>.</param>
        public OptionSettingsBuilder(char shortName, IOptionParametersCount parametersCount)
        {
            this.shortNameAliases.Add(shortName);
            this.parametersCount = parametersCount;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionSettingsBuilder"/> class.
        /// </summary>
        /// <param name="longName">The first long name alias.
        /// All long names aliases have equivalent meaning, but at least one short or long alias must be provided.</param>
        /// <param name="parametersCount">The parameters count policy class. 
        /// Use factory methods from class <see cref="OptionParametersCount"/>.</param>
        public OptionSettingsBuilder(string longName, IOptionParametersCount parametersCount)
        {
            this.longNameAliases.Add(longName);
            this.parametersCount = parametersCount;
        }

        /// <summary>
        /// Creates the <see cref="OptionSettings"/> object from
        /// configured values.
        /// </summary>
        /// <returns>The result of building <see cref="OptionSettings"/></returns>
        public OptionSettings ToOptionSettings()
        {
            return new OptionSettings(
                this.description,
                this.defaultValue,
                this.parameterName,
                this.parametersCount,
                this.longNameAliases,
                this.shortNameAliases,
                this.validators);
        }

        /// <summary>
        /// Sets the parameter name.
        /// Parameter name is used only for help generation. 
        /// </summary>
        /// <remarks>
        /// When the option has parameter name, it's help output will look better e. g.:
        /// <code>-o FILE, --output=FILE</code>
        /// instead of 
        /// <code>-o PARAM, --output=PARAM</code>
        /// </remarks>
        /// <param name="parameterName">The parameter name</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder SetParameterName(string parameterName)
        {
            this.parameterName = parameterName;
            return this;
        }

        /// <summary>
        /// Sets the description of the option.
        /// Description is used for help generation.
        /// </summary>
        /// <param name="description">The description.</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder SetDescription(string description)
        {
            this.description = description;
            return this;
        }

        /// <summary>
        /// Sets the default value of the option.
        /// Default value is used when option is not present in the input.
        /// Setting default value means that the option will be always included in parsing result.
        /// </summary>
        /// <param name="value">The default value.</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder SetDefaultValue(string value)
        {
            this.defaultValue = value;
            return this;
        }

        /// <summary>
        /// Adds another short name alias.
        /// </summary>
        /// <param name="shortNameAlias">The short name alias.</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder AddShortNameAlias(char shortNameAlias)
        {
            this.shortNameAliases.Add(shortNameAlias);
            return this;
        }

        /// <summary>
        /// Adds another long name alias.
        /// </summary>
        /// <param name="longNameAlias">The long name alias.</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder AddLongNameAlias(string longNameAlias)
        {
            this.longNameAliases.Add(longNameAlias);
            return this;
        }

        /// <summary>
        /// Adds the validator for the option.
        /// For adding provided validators like <see cref="IntOptionValidator"/>
        /// one can use specialized methods like <see cref="AsInteger(int,int)"/>.
        /// </summary>
        /// <param name="validator">The validator instance.</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder AddValidator(IOptionValidator validator)
        {
            this.validators.Add(validator);
            return this;
        }

        /// <summary>
        /// Adds the <see cref="IntOptionValidator"/> validator.
        /// </summary>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder AsInteger()
        {
            return this.AddValidator(new IntOptionValidator());
        }

        /// <summary>
        /// Adds the <see cref="IntOptionValidator"/> validator.
        /// </summary>
        /// <param name="min">The minal value (inclusive).</param>
        /// <param name="max">The maximal value (exclusive).</param>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder AsInt(int min, int max)
        {
            return this.AddValidator(new IntOptionValidator(min, max));
        }

        /// <summary>
        /// Adds the <see cref="DoubleOptionValidator"/> validator.
        /// </summary>
        /// <returns>This object instance for easy fluent configuration.</returns>
        public OptionSettingsBuilder AsDouble()
        {
            return this.AddValidator(new DoubleOptionValidator());
        }

        /// <summary>
        /// Adds the <see cref="BoolOptionValidator"/> validator.
        /// </summary>
        /// <returns>This object instance for easy fluent configuration.</returns>        
        public OptionSettingsBuilder AsBool()
        {
            return this.AddValidator(new BoolOptionValidator());
        }
    }
}
